﻿#include "imagetextexporter.h"
#include <QTextDocument>
#include <private/qtextdocument_p.h>
#include <QTextTable>
#include "emotionresource.h"

static QString colorValue(QColor color)
{
    QString result;

    if (color.alpha() == 255) {
        result = color.name();
    } else if (color.alpha()) {
        QString alphaValue = QString::number(color.alphaF(), 'f', 6).remove(QRegExp(QLatin1String("\\.?0*$")));
        result = QString::fromLatin1("rgba(%1,%2,%3,%4)").arg(color.red())
                                                         .arg(color.green())
                                                         .arg(color.blue())
                                                         .arg(alphaValue);
    } else {
        result = QLatin1String("transparent");
    }

    return result;
}


ImageTextExporter::ImageTextExporter(const QTextDocument *_doc)
    : doc(_doc)
{
    const QFont defaultFont = doc->defaultFont();
    defaultCharFormat.setFont(defaultFont);
    // don't export those for the default font since we cannot turn them off with CSS
    defaultCharFormat.clearProperty(QTextFormat::FontUnderline);
    defaultCharFormat.clearProperty(QTextFormat::FontOverline);
    defaultCharFormat.clearProperty(QTextFormat::FontStrikeOut);
    defaultCharFormat.clearProperty(QTextFormat::TextUnderlineStyle);
}

/*!
    Returns the document in HTML format. The conversion may not be
    perfect, especially for complex documents, due to the limitations
    of HTML.
*/
QString ImageTextExporter::toHtml()
{
    html.reserve(doc->docHandle()->length());

    defaultCharFormat = QTextCharFormat();

    QTextFrameFormat rootFmt = doc->rootFrame()->frameFormat();
    rootFmt.clearProperty(QTextFormat::BackgroundBrush);

    QTextFrameFormat defaultFmt;
    defaultFmt.setMargin(doc->documentMargin());

    if (rootFmt == defaultFmt)
        emitFrame(doc->rootFrame()->begin());
    else
        emitTextFrame(doc->rootFrame());

    return html;
}

void ImageTextExporter::emitAttribute(const char *attribute, const QString &value)
{
    html += QLatin1Char(' ');
    html += QLatin1String(attribute);
    html += QLatin1String("=\"");
    html += value.toHtmlEscaped();
    html += QLatin1Char('"');
}

bool ImageTextExporter::emitCharFormatStyle(const QTextCharFormat &format)
{
    bool attributesEmitted = false;

    {
        const QString family = format.fontFamily();
        if (!family.isEmpty() && family != defaultCharFormat.fontFamily()) {
            emitFontFamily(family);
            attributesEmitted = true;
        }
    }

    if (format.hasProperty(QTextFormat::FontPointSize)
        && format.fontPointSize() != defaultCharFormat.fontPointSize()) {
        html += QLatin1String(" font-size:");
        html += QString::number(format.fontPointSize());
        html += QLatin1String("pt;");
        attributesEmitted = true;
    } else if (format.hasProperty(QTextFormat::FontSizeAdjustment)) {
        static const char sizeNameData[] =
            "small" "\0"
            "medium" "\0"
            "xx-large" ;
        static const quint8 sizeNameOffsets[] = {
            0,                                         // "small"
            sizeof("small"),                           // "medium"
            sizeof("small") + sizeof("medium") + 3,    // "large"    )
            sizeof("small") + sizeof("medium") + 1,    // "x-large"  )> compressed into "xx-large"
            sizeof("small") + sizeof("medium"),        // "xx-large" )
        };
        const char *name = 0;
        const int idx = format.intProperty(QTextFormat::FontSizeAdjustment) + 1;
        if (idx >= 0 && idx <= 4) {
            name = sizeNameData + sizeNameOffsets[idx];
        }
        if (name) {
            html += QLatin1String(" font-size:");
            html += QLatin1String(name);
            html += QLatin1Char(';');
            attributesEmitted = true;
        }
    } else if (format.hasProperty(QTextFormat::FontPixelSize)) {
        html += QLatin1String(" font-size:");
        html += QString::number(format.intProperty(QTextFormat::FontPixelSize));
        html += QLatin1String("px;");
        attributesEmitted = true;
    }

    if (format.hasProperty(QTextFormat::FontWeight)
        && format.fontWeight() != defaultCharFormat.fontWeight()) {
        html += QLatin1String(" font-weight:");
        html += QString::number(format.fontWeight() * 8);
        html += QLatin1Char(';');
        attributesEmitted = true;
    }

    if (format.hasProperty(QTextFormat::FontItalic)
        && format.fontItalic() != defaultCharFormat.fontItalic()) {
        html += QLatin1String(" font-style:");
        html += (format.fontItalic() ? QLatin1String("italic") : QLatin1String("normal"));
        html += QLatin1Char(';');
        attributesEmitted = true;
    }

    QLatin1String decorationTag(" text-decoration:");
    html += decorationTag;
    bool hasDecoration = false;
    bool atLeastOneDecorationSet = false;

    if ((format.hasProperty(QTextFormat::FontUnderline) || format.hasProperty(QTextFormat::TextUnderlineStyle))
        && format.fontUnderline() != defaultCharFormat.fontUnderline()) {
        hasDecoration = true;
        if (format.fontUnderline()) {
            html += QLatin1String(" underline");
            atLeastOneDecorationSet = true;
        }
    }

    if (format.hasProperty(QTextFormat::FontOverline)
        && format.fontOverline() != defaultCharFormat.fontOverline()) {
        hasDecoration = true;
        if (format.fontOverline()) {
            html += QLatin1String(" overline");
            atLeastOneDecorationSet = true;
        }
    }

    if (format.hasProperty(QTextFormat::FontStrikeOut)
        && format.fontStrikeOut() != defaultCharFormat.fontStrikeOut()) {
        hasDecoration = true;
        if (format.fontStrikeOut()) {
            html += QLatin1String(" line-through");
            atLeastOneDecorationSet = true;
        }
    }

    if (hasDecoration) {
        if (!atLeastOneDecorationSet)
            html += QLatin1String("none");
        html += QLatin1Char(';');
        attributesEmitted = true;
    } else {
        html.chop(qstrlen(decorationTag.latin1()));
    }

    if (format.foreground() != defaultCharFormat.foreground()
        && format.foreground().style() != Qt::NoBrush) {
        html += QLatin1String(" color:");
        html += colorValue(format.foreground().color());
        html += QLatin1Char(';');
        attributesEmitted = true;
    }

    if (format.background() != defaultCharFormat.background()
        && format.background().style() == Qt::SolidPattern) {
        html += QLatin1String(" background-color:");
        html += colorValue(format.background().color());
        html += QLatin1Char(';');
        attributesEmitted = true;
    }

    if (format.verticalAlignment() != defaultCharFormat.verticalAlignment()
        && format.verticalAlignment() != QTextCharFormat::AlignNormal)
    {
        html += QLatin1String(" vertical-align:");

        QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
        if (valign == QTextCharFormat::AlignSubScript)
            html += QLatin1String("sub");
        else if (valign == QTextCharFormat::AlignSuperScript)
            html += QLatin1String("super");
        else if (valign == QTextCharFormat::AlignMiddle)
            html += QLatin1String("middle");
        else if (valign == QTextCharFormat::AlignTop)
            html += QLatin1String("top");
        else if (valign == QTextCharFormat::AlignBottom)
            html += QLatin1String("bottom");

        html += QLatin1Char(';');
        attributesEmitted = true;
    }

    if (format.fontCapitalization() != QFont::MixedCase) {
        const QFont::Capitalization caps = format.fontCapitalization();
        if (caps == QFont::AllUppercase)
            html += QLatin1String(" text-transform:uppercase;");
        else if (caps == QFont::AllLowercase)
            html += QLatin1String(" text-transform:lowercase;");
        else if (caps == QFont::SmallCaps)
            html += QLatin1String(" font-variant:small-caps;");
        attributesEmitted = true;
    }

    if (format.fontWordSpacing() != 0.0) {
        html += QLatin1String(" word-spacing:");
        html += QString::number(format.fontWordSpacing());
        html += QLatin1String("px;");
        attributesEmitted = true;
    }

    return attributesEmitted;
}

void ImageTextExporter::emitFloatStyle(QTextFrameFormat::Position pos)
{
    if (pos == QTextFrameFormat::InFlow)
        return;

    html += QLatin1String(" style=\"float:");

    if (pos == QTextFrameFormat::FloatLeft)
        html += QLatin1String(" left;");
    else if (pos == QTextFrameFormat::FloatRight)
        html += QLatin1String(" right;");
    else
        Q_ASSERT_X(0, "QTextHtmlExporter::emitFloatStyle()", "pos should be a valid enum type");

    html += QLatin1Char('\"');
}

void ImageTextExporter::emitFontFamily(const QString &family)
{
    html += QLatin1String(" font-family:");

    QLatin1String quote("\'");
    if (family.contains(QLatin1Char('\'')))
        quote = QLatin1String("&quot;");

    html += quote;
    html += family.toHtmlEscaped();
    html += quote;
    html += QLatin1Char(';');
}

void ImageTextExporter::emitFragment(const QTextFragment &fragment)
{
    const QTextCharFormat format = fragment.charFormat();

    bool closeAnchor = false;

    if (format.isAnchor()) {
        const QString name = format.anchorName();
        if (!name.isEmpty()) {
            html += QLatin1String("<a name=\"");
            html += name.toHtmlEscaped();
            html += QLatin1String("\"></a>");
        }
        const QString href = format.anchorHref();
        if (!href.isEmpty()) {
            html += QLatin1String("<a href=\"");
            html += href.toHtmlEscaped();
            html += QLatin1String("\">");
            closeAnchor = true;
        }
    }

    QString txt = fragment.text();
    const bool isObject = txt.contains(QChar::ObjectReplacementCharacter);
    const bool isImage = isObject && format.isImageFormat();

    QLatin1String styleTag("<span style=\"");
    html += styleTag;

    bool attributesEmitted = false;
    if (!isImage)
        attributesEmitted = emitCharFormatStyle(format);
    if (attributesEmitted)
        html += QLatin1String("\">");
    else
        html.chop(qstrlen(styleTag.latin1()));

    if (isObject) {
        for (int i = 0; isImage && i < txt.length(); ++i) {
            QTextImageFormat imgFmt = format.toImageFormat();

            if(imgFmt.hasProperty(QTextFormat::ImageName) &&
                    EmotionResource::instance()->containsName(imgFmt.name()))
            {
                html += QLatin1Char('[') + imgFmt.name() + QLatin1Char(']');;
            }
            else
            {
                html += QLatin1String("<img");

                if (imgFmt.hasProperty(QTextFormat::ImageName))
                    emitAttribute("src", imgFmt.name());

                if (imgFmt.hasProperty(QTextFormat::ImageWidth))
                    emitAttribute("width", QString::number(imgFmt.width()));

                if (imgFmt.hasProperty(QTextFormat::ImageHeight))
                    emitAttribute("height", QString::number(imgFmt.height()));

                if (imgFmt.verticalAlignment() == QTextCharFormat::AlignMiddle)
                    html += QLatin1String(" style=\"vertical-align: middle;\"");
                else if (imgFmt.verticalAlignment() == QTextCharFormat::AlignTop)
                    html += QLatin1String(" style=\"vertical-align: top;\"");

                if (QTextFrame *imageFrame = qobject_cast<QTextFrame *>(doc->objectForFormat(imgFmt)))
                    emitFloatStyle(imageFrame->frameFormat().position());

                html += QLatin1String(" />");
            }

        }
    } else {
        Q_ASSERT(!txt.contains(QChar::ObjectReplacementCharacter));

        txt = txt.toHtmlEscaped();

        // split for [\n{LineSeparator}]
        QString forcedLineBreakRegExp = QString::fromLatin1("[\\na]");
        forcedLineBreakRegExp[3] = QChar::LineSeparator;

        const QStringList lines = txt.split(QRegExp(forcedLineBreakRegExp));
        for (int i = 0; i < lines.count(); ++i) {
            if (i > 0)
                html += QLatin1String("<br />"); // space on purpose for compatibility with Netscape, Lynx & Co.
            html += lines.at(i);
        }
    }

    if (attributesEmitted)
        html += QLatin1String("</span>");

    if (closeAnchor)
        html += QLatin1String("</a>");
}

void ImageTextExporter::emitBlock(const QTextBlock &block)
{
    if (block.begin().atEnd()) {
        // ### HACK, remove once QTextFrame::Iterator is fixed
        int p = block.position();
        if (p > 0)
            --p;
        QTextDocumentPrivate::FragmentIterator frag = doc->docHandle()->find(p);
        QChar ch = doc->docHandle()->buffer().at(frag->stringPosition);
        if (ch == QTextBeginningOfFrame
            || ch == QTextEndOfFrame)
            return;
    }

    if(!html.isEmpty())
        html += QLatin1Char('\n');

    // save and later restore, in case we 'change' the default format by
    // emitting block char format information

    const QTextBlockFormat blockFormat = block.blockFormat();
    if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
        return;
    }

    if (block.begin().atEnd())
        html += QLatin1String("<br />");

    QTextBlock::Iterator it = block.begin();

    for (; !it.atEnd(); ++it)
        emitFragment(it.fragment());
}

void ImageTextExporter::emitTable(const QTextTable *table)
{
    const int rows = table->rows();
    const int columns = table->columns();

    for (int row = 0; row < rows; ++row) {
        for (int col = 0; col < columns; ++col) {
            const QTextTableCell cell = table->cellAt(row, col);

            // for col/rowspans
            if (cell.row() != row)
                continue;

            if (cell.column() != col)
                continue;

            emitFrame(cell.begin());
        }
    }
}

void ImageTextExporter::emitFrame(QTextFrame::Iterator frameIt)
{
    if (!frameIt.atEnd()) {
        QTextFrame::Iterator next = frameIt;
        ++next;
        if (next.atEnd()
            && frameIt.currentFrame() == 0
            && frameIt.parentFrame() != doc->rootFrame()
            && frameIt.currentBlock().begin().atEnd())
            return;
    }

    for (QTextFrame::Iterator it = frameIt;
         !it.atEnd(); ++it) {
        if (QTextFrame *f = it.currentFrame()) {
            if (QTextTable *table = qobject_cast<QTextTable *>(f)) {
                emitTable(table);
            } else {
                emitTextFrame(f);
            }
        } else if (it.currentBlock().isValid()) {
            emitBlock(it.currentBlock());
        }
    }
}

void ImageTextExporter::emitTextFrame(const QTextFrame *f)
{
    emitFrame(f->begin());
}
